package cn.cj.erp.serviceimpl.base;

import cn.cj.erp.common.SQLFilter;
import cn.cj.erp.common.annotation.Search;
import cn.cj.erp.common.enums.SearchType;
import cn.cj.erp.common.exception.ServiceException;
import cn.cj.erp.common.Page;
import cn.cj.erp.common.SearchParam;
import cn.cj.erp.common.utils.Assert;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.enums.IEnum;
import cn.cj.erp.service.base.BaseService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.service.additional.query.impl.QueryChainWrapper;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * 基础Service实现类
 *
 * @author jianchen
 * @version 1.0.0
 * @date 2024/8/1
 */
public abstract class ServiceImpl<Entity, Bo> implements BaseService<Entity, Bo> {

    @Override
    public Page<Bo> queryPage(Bo params) {
        if (!SearchParam.class.isAssignableFrom(params.getClass())) {
            throw new ServiceException("query page params is not assignable from " + SearchParam.class);
        }
        SearchParam searchParam = (SearchParam) params;
        IPage<Entity> page = convertQueryParam(searchParam).page(convertPage(searchParam));
        return new Page<>(convert2BoList(page.getRecords()), page);
    }

    @Override
    public List<Bo> queryList(Bo params) {
        if (!SearchParam.class.isAssignableFrom(params.getClass())) {
            throw new ServiceException("query page params is not assignable from " + SearchParam.class);
        }
        SearchParam searchParam = (SearchParam) params;
        List<Entity> list = convertQueryParam(searchParam).list();
        return convert2BoList(list);
    }

    @Override
    public boolean save(Bo bo) {
        return getBaseMapper().insert(convert2Entity(bo)) > 0;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveBatch(List<Bo> boList) {
        try {
            for (Bo bo : boList) {
                if (!this.save(bo)) {
                    this.rollback();
                    return false;
                }
            }
            return true;
        } catch (Exception e) {
            this.rollback();
            return false;
        }
    }

    @Override
    public boolean removeById(Long id) {
        return getBaseMapper().deleteById(id) > 0;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean removeByIds(List<Long> ids) {
        try {
            for (Long id : ids) {
                if (!this.removeById(id)) {
                    this.rollback();
                    return false;
                }
            }
            return true;
        } catch (Exception e) {
            this.rollback();
            return false;
        }
    }

    @Override
    public boolean updateById(Bo bo) {
        return getBaseMapper().updateById(convert2Entity(bo)) > 0;
    }

    @Override
    public boolean updateBatchById(List<Bo> boList) {
        try {
            for (Bo bo : boList) {
                if (!this.updateById(bo)) {
                    this.rollback();
                    return false;
                }
            }
            return true;
        } catch (Exception e) {
            this.rollback();
            return false;
        }
    }

    @Override
    public Bo getById(Long id) {
        return convert2Bo(getBaseMapper().selectById(id));
    }

    @Override
    public List<Bo> listByIds(List<Long> ids) {
        return convert2BoList(getBaseMapper().selectBatchIds(ids));
    }

    /**
     * 事务回滚标记
     * <p>当注解{@link Transactional}的函数，执行完成后，进行事务回滚</p>
     */
    protected void rollback() {
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }

    // --------------------------------分页查询处理--------------------------------
    /**
     * params -> MyBase-Plus IPage对象
     * @param params 搜索参数
     * @return MyBase-Plus IPage对象
     */
    public IPage<Entity> convertPage(SearchParam params) {
        //分页参数
        long curPage = 1;
        long limit = 10;

        if (params.getPageNum() != null) {
            curPage = params.getPageNum();
        }
        if (params.getPageSize() != null) {
            limit = params.getPageSize();
        }

        //分页对象
        com.baomidou.mybatisplus.extension.plugins.pagination.Page<Entity> page = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(curPage, limit);

        //排序字段
        //防止SQL注入（因为sidx、order是通过拼接SQL实现排序的，会有SQL注入风险）
        String orderField = SQLFilter.sqlInject(params.getSidx());
        String order = params.getOrder();
        //前端字段排序
        if (StrUtil.isNotEmpty(orderField) && StrUtil.isNotEmpty(order)) {
            if (SearchParam.ASC.equalsIgnoreCase(order)) {
                return page.addOrder(OrderItem.asc(orderField));
            } else {
                return page.addOrder(OrderItem.desc(orderField));
            }
        }

        return page;
    }

    /**
     * params -> QueryWrapper对象
     * @param param 搜索参数
     * @return QueryWrapper对象
     */
    public QueryChainWrapper<Entity> convertQueryParam(SearchParam param) {
        QueryChainWrapper<Entity> queryWrapper = query();

        Field[] fields = ReflectUtil.getFields(param.getClass());
        // 需要进行 apply 操作符的字段
        List<Wrapper> applyList = new ArrayList<>();
        Wrapper defSortField = null;
        for (Field field : fields) {
            if (!field.isAnnotationPresent(Search.class)) {
                continue;
            }
            if (field.getName().equals("sqlFilter")) {
                continue;
            }
            Search search = field.getAnnotation(Search.class);
            // 缓存默认排序字段
            if (search.defaultSortField()) {
                if (defSortField == null) {
                    defSortField = new Wrapper(field, null, search);
                } else {
                    throw new ServiceException("default sort field only one, but multiple.");
                }
            }
            // get field value
            Object value = ReflectUtil.getFieldValue(param, field);
            if (value == null) {
                continue;
            }
            if ((value instanceof Collection) && CollectionUtil.isEmpty((Collection<?>) value)) {
                continue;
            }
            if ((value instanceof String) && StrUtil.isBlank((String) value)) {
                continue;
            }
            String fieldName = getFieldName(field, search);
            // 字段值是枚举还是枚举列表
            if (isEnumField(field) || isEnumListField(field)) {
                handleEnumField(queryWrapper, field, search, fieldName, value);
                continue;
            }
            // apply预保存
            if (SearchType.APPLY.equals(search.type())) {
                if (StrUtil.isBlank(search.apply())) {
                    throw new ServiceException("apply operation need apply string");
                }
                applyList.add(new Wrapper(field, value, search));
                continue;
            }
            handleField(queryWrapper, search, fieldName, value);
        }
        // apply操作放在其他操作符之后进行
        for (Wrapper wrapper : applyList) {
            String applyStr = wrapper.getSearch().apply();
            // 占位符替换
            if (StrUtil.contains(applyStr, "{}")) {
                applyStr = StrUtil.replace(applyStr, "{}", wrapper.getValue().toString());
            }
            queryWrapper.apply(" " + applyStr + " ");
        }
        // 数据权限过滤处理
        Field sqlFilterField = ReflectUtil.getField(param.getClass(), "sqlFilter");
        if (sqlFilterField != null) {
            String sqlFilter = (String) ReflectUtil.getFieldValue(sqlFilterField, "sqlFilter");
            if (StrUtil.isNotBlank(sqlFilter)) {
                queryWrapper.apply(sqlFilter);
            }
        }
        // 默认排序字段处理
        if (StrUtil.isEmpty(param.getSidx()) && defSortField != null) {
            String fieldName = getFieldName(defSortField.getField(), defSortField.getSearch());
            queryWrapper.orderBy(true, defSortField.getSearch().defaultSortAsc(), fieldName);
        }
        return queryWrapper;
    }

    /**
     * 获取数据库字段名称
     * @param field 反射Field对象
     * @param search 注解对象
     * @return 数据库字段名称
     */
    private String getFieldName(Field field, Search search) {
        String fieldName;
        if (StrUtil.isNotBlank(search.fieldName())) {
            fieldName = search.fieldName();
        } else {
            // 默认则使用下划线方式
            fieldName = StrUtil.toUnderlineCase(field.getName());
        }
        return fieldName;
    }

    /**
     * 是否枚举字段
     * @param field 反射对象
     * @return true-枚举字段，false-非枚举字段
     */
    private boolean isEnumField(Field field) {
        return IEnum.class.isAssignableFrom(field.getType());
    }

    /**
     * 是否是枚举列表
     * @param field 反射对象
     * @return true-枚举列表，false-非枚举类别
     */
    private boolean isEnumListField(Field field) {
        if (!Collection.class.isAssignableFrom(field.getType())) {
            return false;
        }
        Type genericType = field.getGenericType();
        if (genericType instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) genericType;
            Type actualType = parameterizedType.getActualTypeArguments()[0];
            if (actualType instanceof Class) {
                return IEnum.class.isAssignableFrom((Class<?>) actualType);
            }
        }
        return false;
    }


    /**
     * 枚举字段处理（枚举类型或枚举列表）
     *
     * @param queryWrapper Mybatis-Plus QueryWrapper
     * @param search 注解对象
     * @param fieldName 数据库字段名称
     * @param value 字段值
     */
    private void handleEnumField(QueryChainWrapper<Entity> queryWrapper,
                                        Field field,
                                        Search search,
                                        String fieldName,
                                        Object value) {
        if (isEnumField(field)) {
            // 枚举类型，必须是EQ
            Assert.isTrue(SearchType.EQ == search.type(),
                    StrUtil.format("[{}.{}] is enum, searchType must be EQ, but now is {}",
                            field.getType().getName(), field.getName(), search.type()));
            IEnum<?> iEnum = (IEnum<?>) value;
            queryWrapper.eq(fieldName, iEnum.getValue());
            return;
        }
        if (isEnumListField(field)) {
            // 枚举列表,必须是IN
            Assert.isTrue(SearchType.IN == search.type(),
                    StrUtil.format("[{}.{}] is enum list, searchType must be IN, but now is {}",
                            field.getType().getName(), field.getName(), search.type()));
            Collection<?> collection = (Collection<?>) value;
            List<Object> values = new ArrayList<>();
            for (Object element : collection) {
                IEnum<?> iEnum = (IEnum<?>) element;
                values.add(iEnum.getValue());
            }
            queryWrapper.in(fieldName, values);
        }
    }

    /**
     * 字段处理
     *
     * @param queryWrapper Mybatis-Plus QueryWrapper
     * @param search 注解对象
     * @param fieldName 数据库字段名称
     * @param value 字段值
     */
    private void handleField(QueryChainWrapper<Entity> queryWrapper, Search search, String fieldName, Object value) {
        // 操作符处理
        if (SearchType.EQ.equals(search.type())) {
            queryWrapper.eq(fieldName, value);
        } else if (SearchType.LIKE.equals(search.type())) {
            queryWrapper.like(fieldName, value);
        } else if (SearchType.LEFT_LIKE.equals(search.type())) {
            queryWrapper.likeLeft(fieldName, value);
        } else if (SearchType.GE.equals(search.type())) {
            queryWrapper.ge(fieldName, value);
        } else if (SearchType.LE.equals(search.type())) {
            queryWrapper.le(fieldName, value);
        } else if (SearchType.GT.equals(search.type())) {
            queryWrapper.gt(fieldName, value);
        } else if (SearchType.LT.equals(search.type())) {
            queryWrapper.lt(fieldName, value);
        } else if (SearchType.BETWEEN_BEGIN.equals(search.type())) {
            queryWrapper.ge(fieldName, value);
        } else if (SearchType.BETWEEN_END.equals(search.type())) {
            queryWrapper.le(fieldName, value);
        } else if (SearchType.IN.equals(search.type())) {
            queryWrapper.in(fieldName, (Collection<?>) value);
        } else {
            String errorMessage = StrUtil.format("[{}] searchType is unknown: {}",fieldName, search.type());
            throw new ServiceException(errorMessage);
        }
    }

    @Data
    @AllArgsConstructor
    private static class Wrapper {
        private Field field;
        private Object value;
        private Search search;
    }
}